/*
 * File     : DirectNativeInterface.java
 * Created  : 1 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.googlecode.dni.callback.FunctionPointerFactory;
import com.googlecode.dni.callback.IllegalCallbackException;
import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.library.Function;
import com.googlecode.dni.library.IllegalLibraryException;
import com.googlecode.dni.library.Library;
import com.googlecode.dni.platform.PlatformGroup;
import com.googlecode.dni.platform.PlatformType;
import com.googlecode.dni.type.NativeString;
import com.googlecode.dni.type.NativeStringBuffer;
import com.googlecode.dni.type.Pointer;
import com.googlecode.dni.type.Region;
import com.googlecode.dni.type.cenum.CEnumValue;
import com.googlecode.dni.type.cenum.EnumBitFieldHelper;
import com.googlecode.dni.type.cenum.MaskBitFieldHelper;
import com.googlecode.dni.type.structure.IllegalStructureException;
import com.googlecode.dni.type.structure.Structure;
import com.googlecode.dni.type.structure.StructureFactory;
import com.googlecode.dni.type.structure.StructureInfo;


/**
 * <p>
 *  Provides the top-level entry point for Direct Native Interface.
 * </p>
 * <p>
 *  Direct Native Interface (DNI) is design to provide quick and easy access to
 *  native (non-Java) functions from Java, without the requirement to manually
 *  write JNI stubs to call the functions.  Therefore, DNI supports
 *  <em>structures</em> that access native memory in the same manner as C
 *  <code>struct</code>s, and <em>libraries</em> of functions.  Various
 *  different calling conventions are supported.
 * </p>
 * <p>
 *  This class acts as the main static factory for various types.  DNI places an
 *  emphasis on interfaces over classes.  Interface implementations are chosen
 *  based on the run-time details, and many (structures and libraries) are
 *  generated on the fly.  You should avoid implementing the various interfaces
 *  in client code; DNI makes assumptions about the implementations (and their
 *  behaviour) in various places.
 * </p>
 *
 * @see Structure
 * @see Library
 * @see <a href="http://code.google.com/p/direct-native-interface/">Project Home</a>
 *
 * @author Matthew Wilson
 */
public final class DirectNativeInterface
{

    /** Logger for DNI. */
    private static final Logger LOGGER = Logger.getLogger( "DNI" );

    private static final Map< InitialisationOption, Object > OPTIONS;

    static
    {
        // Do this once, and once only.
        try
        {
            System.loadLibrary( "dni" );
        }
        catch ( Throwable throwable )
        {
            LOGGER.log( Level.SEVERE, "Failed to load DNI library", throwable );
            throw new ExceptionInInitializerError( throwable );
        }

        // Pick up the initialisation options, if set.
        OPTIONS = InitialisationOption.Initialisation.getInitialisationOptions();

        LOGGER.info( "DNI initialised" );
    }

    /** Prevents instantiation. */
    private DirectNativeInterface()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * <p>
     *  Creates an instance of a library API, given its type.
     * </p>
     * <p>
     *  The native library specified by the {@link Library} annotation is first
     *  loaded, then an implementation of the given interface is created that
     *  will call the method's equivalent native function.
     * </p>
     * <p>
     *  See the class documentation for details of how interface methods require
     *  annotation to specify the native function's details.
     * </p>
     *
     * @param <T>
     *            the type of interface
     * @param type
     *            the type of interface
     *
     * @return an implementation of the given interface
     *
     * @throws LinkageError
     *             if the library or libraries could not be loaded
     * @throws IllegalLibraryException
     *             if the given type is not an interface, or is not correctly
     *             annotated
     */
    public static < T > T getLibrary( final Class< T > type ) throws LinkageError
    {
        return type.cast( DniInternal.getLibraryRecord( type ).getLibrary() );
    }

    // TODO v1.1 method passing in the various Library options
    // -- which presents problems, such as creating new types that look like
    //    annotations, and how to manage conflicting info from annotation and
    //    explicit info, especially if already created the library

    /**
     * <p>
     *  Obtains an abstract factory that will create structures of the given
     *  type.
     * </p>
     *
     * @param <T>
     *            the type of structure
     * @param type
     *            the type of structure
     *
     * @return a factory for the structure
     *
     * @throws IllegalStructureException
     *             if the given type is not an interface, or is not correctly
     *             annotated
     */
    public static < T extends Structure > StructureFactory< T > getStructureFactory( final Class< T > type )
    {
        @SuppressWarnings( "unchecked" )
        StructureFactory< T > factory = (StructureFactory< T >) DniInternal.getStructureRecord( type ).getFactory();
        return factory;
    }

    /**
     * <p>
     *  Obtains information about the given structure.
     * </p>
     *
     * @param type
     *            the structure type
     *
     * @return structure information
     */
    public static StructureInfo getStructureInfo( final Class< ? extends Structure > type )
    {
        return DniInternal.getStructureRecord( type ).getInfo();
    }

    /**
     * <p>
     *  Obtains an abstract factory that will create function pointers of the
     *  given type.
     * </p>
     *
     * @param <T>
     *            the type of call-back
     * @param type
     *            the type of call-back
     *
     * @return a factory for the type
     *
     * @throws IllegalCallbackException
     *             if the type is not an interface, does not have precisely one
     *             method, or is not correctly annotated
     */
    public static < T > FunctionPointerFactory< T > getFunctionPointerFactory( final Class< T > type )
    {
        return DniInternal.getFunctionPointerFactory( type );
    }

    /**
     * <p>
     *  Allocates a buffer backed by native memory.
     * </p>
     * <p>
     *  The buffers memory <em>must</em> be explicitly freed by calling
     *  {@link #freeBuffer(Buffer)}.
     * </p>
     * <p>
     *  Unlike in OpenJDK, the buffer's address is not guaranteed to be aligned
     *  to a page boundary.
     * </p>
     *
     * @param size
     *            the size in bytes (must be greater than zero)
     * @return a buffer
     */
    public static ByteBuffer allocateBuffer( final int size )
    {
        if ( size <= 0 )
        {
            throw new IllegalArgumentException( "Bad size: " + size );
        }

        return DniInternal.ALLOCATOR.allocate( size ).getBuffer( size );
    }

    /**
     * <p>
     *  Frees a buffer allocated by {@link #allocateBuffer(int)}.
     * </p>
     * <p>
     *  YOU MUST NOT CALL THIS WITH JAVA-ALLOCATED {@link Buffer}s.  You will
     *  get random errors on a finalizer threads (double-freeing).
     * </p>
     *
     * @param buffer
     *            the buffer
     */
    public static void freeBuffer( final Buffer buffer )
    {
        if ( buffer != null )
        {
            DniInternal.ALLOCATOR.free( Pointer.fromBuffer( buffer ) );
        }
    }

    /**
     * <p>
     *  Converts the given Java string into a native string in the platform's
     *  default charset.
     * </p>
     * <p>
     *  Temporary memory is allocated as required.  The string must be freed
     *  when no longer needed to release that memory.
     * </p>
     *
     * @param string
     *            the string
     *
     * @return a native string; <code>null</code> if the string is null
     */
    public static NativeString wrapString( final String string )
    {
        return DniInternal.wrapString( string, Library.DEFAULT_CHARSET );
    }

    /**
     * <p>
     *  Converts the given Java string into a native string in the given
     *  charset.
     * </p>
     * <p>
     *  Temporary memory is allocated as required.  The string must be freed
     *  when no longer needed to release that memory.
     * </p>
     *
     * @param string
     *            the string
     * @param charset
     *            the charset, or {@link Library#DEFAULT_CHARSET}
     *            or {@link Library#WIDE_CHARSET}
     *
     * @return a native string; <code>null</code> if the string is null
     */
    public static NativeString wrapString( final String string,
                                           final String charset )
    {
        if ( string == null )
        {
            return null;
        }

        return DniInternal.wrapString( string, charset );
    }

    /**
     * <p>
     *  Wraps the given null-terminated native string.
     * </p>
     * <p>
     *  The returned string can be freed, provided that the native memory can be
     *  legitimately freed by the current {@link Allocator}.
     * </p>
     * <p>
     *  No reading of memory is done at the point of this call.
     * </p>
     *
     * @param pointer
     *            the pointer to the first character of the string
     *
     * @return a native string; <code>null</code> if the pointer is null
     */
    public static NativeString wrapString( final Pointer pointer )
    {
        if ( Pointer.isNull( pointer ) )
        {
            return null;
        }

        return DniInternal.wrapString( pointer, Integer.MAX_VALUE );
    }

    /**
     * <p>
     *  Wraps the given null-terminated native string, with an upper bound on
     *  the size.
     * </p>
     * <p>
     *  The returned string can be freed, provided that the native memory can be
     *  legitmately freed by the current {@link Allocator}.
     * </p>
     * <p>
     *  No reading of memory is done at the point of this call.
     * </p>
     *
     * @param region
     *            the region of memory for the string
     *
     * @return a native string; <code>null</code> if the region is null
     */
    public static NativeString wrapString( final Region region )
    {
        if ( region == null || region.pointer() == null )
        {
            return null;
        }

        return DniInternal.wrapString( region.pointer(), region.getSize() );
    }

    /**
     * <p>
     *  Creates a string buffer.
     * </p>
     *
     * @param charset
     *            the charset to use; or {@link Library#DEFAULT_CHARSET}
     *            or {@link Library#WIDE_CHARSET}
     * @param capacity
     *            the capacity of the buffer, in bytes; must be at least 4
     *
     * @return a native string
     */
    public static NativeStringBuffer createStringBuffer( final int capacity, final String charset )
    {
        return DniInternal.createStringBuffer( capacity, charset );
    }

    /**
     * <p>
     *  Creates a string buffer from existing memory.
     * </p>
     *
     * @param pointer
     *            the pointer to existing memory
     * @param capacity
     *            the capacity of the buffer, in bytes; must be at least 4
     * @param charset
     *            the charset to use; or {@link Library#DEFAULT_CHARSET}
     *            or {@link Library#WIDE_CHARSET}
     *
     * @return a native string; or <code>null</code> if the pointer is null
     */
    public static NativeStringBuffer createStringBuffer( final Pointer pointer, final int capacity, final String charset )
    {
        if ( Pointer.isNull( pointer ) )
        {
            return null;
        }

        return DniInternal.createStringBuffer( pointer, capacity, charset );
    }

    /**
     * <p>
     *  Creates a helper for dealing with a bit field segment containing an
     *  enum value.
     * </p>
     * <p>
     *  The Java enum will be treated as though it were passed into a library
     *  method; that is, the values should be annotated with {@link CEnumValue}
     *  as appropriate.
     * </p>
     * <p>
     *  The field segment holds a single value from a range of mutually
     *  exclusive values (as defined by the enum).
     * </p>
     * <p>
     *  See the {@link EnumBitFieldHelper} class documentation for more
     *  information.  It is best to store the helper as a constant within the
     *  enum's type or another appropriate place.
     * </p>
     *
     * @param <E>
     *            the Java enum type
     * @param enumType
     *            the Java enum type
     * @param mask
     *            the bit mask to use; every bit that is used in determining the
     *            enum value must be set
     * @param offset
     *            the bit offset (0 to 31 inclusive) to apply to the enum's
     *            values; that is, the enum's value will be shifted left this
     *            number of bits before insertion into the bit field
     * @return a helper
     *
     * @see EnumBitFieldHelper
     */
    public static < E extends Enum< E > >
    EnumBitFieldHelper< E > getEnumBitFieldHelper( final Class< E > enumType, final int mask, final int offset )
    {
        return DniInternal.getEnumBitFieldHelper( enumType, mask, offset );
    }

    /**
     * <p>
     *  Creates a helper for dealing with a bit field segment containing a mask.
     * </p>
     * <p>
     *  The Java enum will be treated as though it were passed into a library
     *  method; that is, the values should be annotated with {@link CEnumValue}
     *  as appropriate.
     * </p>
     * <p>
     *  The field segment holds a number of independent boolean flags, with each
     *  flag defined by an enum value.
     * </p>
     * <p>
     *  See the {@link MaskBitFieldHelper} class documentation for more
     *  information.  It is best to store the helper as a constant within the
     *  enum's type or another appropriate place.
     * </p>
     *
     * @param <E>
     *            the Java enum type
     * @param enumType
     *            the Java enum type
     * @param offset
     *            the bit offset (0 to 31 inclusive) to apply to the enum's
     *            values; that is, the enum's value will be shifted left this
     *            number of bits before insertion into the bit field
     * @return a helper
     *
     * @see MaskBitFieldHelper
     */
    public static < E extends Enum< E > >
    MaskBitFieldHelper< E > getMaskBitFieldHelper( final Class< E > enumType, final int offset )
    {
        return DniInternal.getMaskBitFieldHelper( enumType, offset );
    }

    /**
     * <p>
     *  Reads the most-recently set 'last error' value on this thread.
     * </p>
     *
     * @return the last error value
     *
     * @see Function#lastError()
     */
    public static int getLastError()
    {
        return DniInternal.getLastError();
    }

    /**
     * <p>
     *  Resets the 'last error' value on this thread.
     * </p>
     *
     * @see Function#lastError()
     */
    public static void clearLastError()
    {
        DniInternal.clearLastError();
    }

    /**
     * <p>
     *  Determines the current platform group.
     * </p>
     *
     * @return the current platform group
     */
    public static PlatformGroup getPlatformGroup()
    {
        return DniInternal.PLATFORM.getPlatformGroup();
    }

    /**
     * <p>
     *  Determines the current platform type.
     * </p>
     *
     * @return the current platform type
     */
    public static PlatformType getPlatformType()
    {
        return DniInternal.PLATFORM.getPlatformType();
    }

    /**
     * <p>
     *  Obtains an allocator of native memory.
     * </p>
     *
     * @return an allocator
     */
    public static Allocator getAllocator()
    {
        return DniInternal.ALLOCATOR;
    }

    /**
     * <p>
     *  Obtains an option set during initialisation.
     * </p>
     *
     * @param option
     *            the option type
     *
     * @return the option's value
     */
    public static Object getOption( final InitialisationOption option )
    {
        return OPTIONS.get( option );
    }

    /**
     * Forces this class to be initialised.
     */
    static void init()
    {
        // nothing
    }

}
